package com.github.tommas.admintpl.service;

import com.github.tommas.admintpl.bean.model.Role;
import com.github.tommas.admintpl.bean.model.RolePermission;
import com.github.tommas.admintpl.bean.model.User;
import com.github.tommas.admintpl.bean.vo.AuthorizationInfo;
import com.github.tommas.admintpl.dao.PermissionMapper;
import com.github.tommas.admintpl.dao.RoleMapper;
import com.github.tommas.admintpl.dao.UserMapper;
import com.github.tommas.admintpl.security.UserPrincipal;
import com.github.tommas.admintpl.security.permission.RestrictedPermission;
import com.github.tommas.admintpl.security.permission.RestrictedPermissionResolver;
import org.apache.shiro.crypto.hash.Hash;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.*;

@Service
public class AccountService {
    @Value("${security.password-hash-algorithm:SHA-256}")
    private String passwordHashAlgorithm;

    @Autowired
    UserMapper userMapper;

    @Autowired
    RoleMapper roleMapper;

    @Autowired
    PermissionMapper permissionMapper;

    public String getPasswordHashAlgorithm() {
        return passwordHashAlgorithm;
    }

    public Hash getHashedPassword(String raw, String username) {
        return new SimpleHash(passwordHashAlgorithm, raw, username);
    }

    public User login(String username, String hashedPass) {
        User user = userMapper.findUserWithPassword(username, hashedPass);
        if (user == null) {
            return null;
        }
        return user;
    }

    public AuthorizationInfo getUserAuthorizationInfo(UserPrincipal userPrincipal) {
        return getUserAuthorizationInfo(userPrincipal.getUsername(), userPrincipal.getUserId());
    }

    public AuthorizationInfo getUserAuthorizationInfo(String username, int userId) {
        return getAuthorizationInfoForUser(userId);
    }

    private AuthorizationInfo getAuthorizationInfoForUser(int userId) {
        List<Role> roles = roleMapper.findUserRoles(userId);
        if (roles.isEmpty()) {
            return null;
        }

        AuthorizationInfo info = new AuthorizationInfo();
        List<Integer> roleIds = new ArrayList<>(roles.size());
        for (Role role : roles) {
            roleIds.add(role.getId());
        }
        info.setRoles(roles);

        List<RolePermission> rolePermissions = permissionMapper.findPermissionsByRoles(roleIds);
        if (!rolePermissions.isEmpty()) {
            info.setPermissions(new ArrayList<>(mergeRolePermissions(rolePermissions)));
        }

        return info;
    }

    private Collection<RestrictedPermission> mergeRolePermissions(List<RolePermission> rolePermissions) {
        Map<String, RestrictedPermission> permissionMap = new HashMap<>();
        for (RolePermission rolePermission : rolePermissions) {
            String perm = rolePermission.getPermission();
            RestrictedPermission permission = permissionMap.get(perm);
            if (permission == null) {
                if (StringUtils.hasText(rolePermission.getRestrictions())) {
                    String[] restrictions = rolePermission.getRestrictions().trim().split(RestrictedPermissionResolver.ATTRIBUTE_DIVIDER);
                    permission = new RestrictedPermission(perm, new HashSet<>(Arrays.asList(restrictions)));
                    permission.addRestrictions(restrictions);
                } else {
                    permission = new RestrictedPermission(perm);
                }

                permissionMap.put(perm, permission);
                continue;
            }

            // continue, if previous permission is not restricted
            if (!permission.hasRestriction()) {
                continue;
            }

            if (!StringUtils.hasText(rolePermission.getRestrictions())) {
                // replace with a non restricted permission if current permission is not restricted
                permissionMap.put(perm, new RestrictedPermission(perm));
            } else {
                // merge restrictions of two permissions
                String[] restrictions = rolePermission.getRestrictions().trim().split(RestrictedPermissionResolver.ATTRIBUTE_DIVIDER);
                permission.addRestrictions(restrictions);
            }
        }

        return permissionMap.values();
    }
}